﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace Tunneler {
    public class Terrain {
        GraphicsDevice graphics;
        List<Texture2D> textures;
        Texture2D texture;

        Color[][] terrainTexturesData;
        Texture2D[] terrainTextures;

        // Deafault Values
        static int width = 4000;
        static int height = 4000;

        string[] textureNames = { "backgroundGrass", "backgroundStone", "backgroundEarth", "backgroundMolten", 
                                    "backgroundMoltenEarth", "backgroundSand", "backgroundSandSimple", "backgroundStone"};

        public int Width { get { return width; } }
        public int Height { get { return height; } }

        public Texture2D[] TerrainTextures { get { return terrainTextures; } }
        public Color[][] TerrainTextturesData { get { return terrainTexturesData; } }


        public Terrain(GraphicsDevice graphicsDevice) {
            graphics = graphicsDevice;

            textures = new List<Texture2D>();
        }

        // ==========
        // Public API
        // ==========

        /// <summary>
        /// Possible levels: 0,1,2
        /// </summary>
        public static void SetSizeLevel(int level) {
            switch (level) {
                case 0:
                    width = 2000;
                    height = 2000;
                    break;
                case 1:
                    width = 3000;
                    height = 3000;
                    break;
                case 2:
                    width = 4000;
                    height = 4000;
                    break;
                default:
                    throw new ArgumentException("Invalit size level");
            }
        }

        public void Draw(SpriteBatch spriteBatch) {
            int bgWidth = Width / texture.Width + 1;
            int bgHeight = Height / texture.Height + 1;
            for (int i = 0; i < bgWidth; ++i) {
                for (int j = 0; j < bgHeight; ++j) {
                    Texture2D drawnTexture = terrainTextures[i + j * bgWidth];
                    int px = i * texture.Width;
                    int py = j * texture.Height;

                    Vector2 position = new Vector2(px, py);
                    Vector2 origin = new Vector2(0, 0);
                    spriteBatch.Draw(drawnTexture, position, null, Color.White, 0f, origin, 1f, SpriteEffects.None, 0);
                }
            }
        }

        public void LoadContent(ContentManager content) {
            foreach (string tName in textureNames) {
                textures.Add(content.Load<Texture2D>(tName));
            }
        }

        public void Reset() {
            Random r = new Random();
            int randomIndex = r.Next(textureNames.Length - 1);
            texture = textures[randomIndex];

            Color[] textureData = new Color[texture.Width * texture.Height];
            texture.GetData<Color>(textureData);

            int bgWidth = Width / texture.Width + 1;
            int bgHeight = Height / texture.Height + 1;
            terrainTexturesData = new Color[bgWidth * bgHeight][];
            terrainTextures = new Texture2D[bgWidth * bgHeight];
            for (int row = 0; row < bgWidth; ++row) {
                for (int col = 0; col < bgHeight; ++col) {
                    int index = row + col * bgWidth;
                    terrainTexturesData[index] = (Color[])textureData.Clone();
                    terrainTextures[index] = new Texture2D(graphics, texture.Width, texture.Height);
                    terrainTextures[index].SetData<Color>(terrainTexturesData[index]);
                }
            }
        }

        public void TouchedBy(IGameObject gameObject) {
            int x = GetStartingGlobalX(gameObject);
            int y = GetStartingGlobalY(gameObject);

            int rowStart = x / texture.Width;
            int rowEnd = (int)(x + gameObject.Texture.Width * gameObject.TextureScale) / texture.Width;
            int colStart = y / texture.Height;
            int colEnd = (int)(y + gameObject.Texture.Height * gameObject.TextureScale) / texture.Height;

            for (int i = rowStart; i <= rowEnd; ++i) {
                for (int j = colStart; j <= colEnd; ++j) {
                    if (gameObject is PlayerBase) {
                        TouchedBy((PlayerBase)gameObject, i, j);
                    } else if (gameObject is Player) {
                        TouchedBy((Player)gameObject, i, j);
                    } else if (gameObject is LaserShot) {
                        TouchedBy((LaserShot)gameObject, i, j);
                    } else if (gameObject is Explosion) {
                        TouchedBy((Explosion)gameObject, i, j);
                    } else if (gameObject is Artefact) {
                        TouchedBy((Artefact)gameObject, i, j);
                    }
                }
            }
        }

        public Tuple<List<Texture2D>, List<Color[]>, List<Vector2>> GetBoundingTexturesAndData(IGameObject gameObject) {
            List<Texture2D> textures = new List<Texture2D>();
            List<Color[]> texturesData = new List<Color[]>();
            List<Vector2> texturePositions = new List<Vector2>();

            int x = GetStartingGlobalX(gameObject);
            int y = GetStartingGlobalY(gameObject);

            int rowStart = x / texture.Width;
            int rowEnd = (x + gameObject.Texture.Width) / texture.Width;
            int colStart = y / texture.Height;
            int colEnd = (y + gameObject.Texture.Height) / texture.Height;


            int bgWidth = Width / texture.Width + 1;
            for (int i = rowStart; i <= rowEnd; ++i) {
                for (int j = colStart; j <= colEnd; ++j) {
                    textures.Add(terrainTextures[i + j * bgWidth]);
                    texturesData.Add(terrainTexturesData[i + j * bgWidth]);
                    texturePositions.Add(new Vector2(i * texture.Height, j * texture.Width));
                }
            }

            return new Tuple<List<Texture2D>, List<Color[]>, List<Vector2>>(textures, texturesData, texturePositions);
        }

        // ===============
        // Private Methods
        // ===============

        private void TouchedBy(PlayerBase playerBase, int row, int col) {
            ClearTerrainSharedRectangle(playerBase, row, col);
        }

        private void TouchedBy(Player player, int row, int col) {
            ClearTerrainSharedRectangle(player, row, col);
        }

        private void TouchedBy(Explosion explosion, int row, int col) {
            ClearTerrainSharedRectangle(explosion, row, col);
        }

        private void TouchedBy(LaserShot shot, int row, int col) {
            // Currently no behaviour as the shot explodes
        }

        private void TouchedBy(Artefact artefact, int row, int col) {
            ClearTerrainSharedRectangle(artefact, row, col);
        }


        /// <summary>
        /// Clears terrain occupied by game object's texture's bounding rectangle.
        /// </summary>
        private void ClearTerrainSharedRectangle(IGameObject gameObject, int row, int col) {
            int x = GetStartingGlobalX(gameObject);
            int y = GetStartingGlobalY(gameObject);

            int tx = x % texture.Width;
            int ty = y % texture.Height;

            int tWidthThisRow = (int)(gameObject.Texture.Width * gameObject.TextureScale);
            int tHeightThisCol = (int)(gameObject.Texture.Width * gameObject.TextureScale);

            if (x < row * texture.Width) {
                tx = 0;
                int dif = row * texture.Width - x;
                tWidthThisRow -= dif;
            }

            if (y < col * texture.Height) {
                ty = 0;
                int dif = col * texture.Height - y;
                tHeightThisCol -= dif;
            }

            int bgWidth = Width / texture.Width + 1;
            Color[] touchedTextureData = terrainTexturesData[row + col * bgWidth];

            bool changed = false;
            for (int i = 0; i < tWidthThisRow; ++i) {
                for (int j = 0; j < tHeightThisCol; ++j) {
                    x = i + tx;
                    y = j + ty;
                    if (x >= texture.Width || y >= texture.Height) {
                        continue;
                    }
                    int index = x + y * texture.Width;
                    if (touchedTextureData[index].A != 0) {
                        touchedTextureData[index] = new Color();
                        changed = true;
                    }
                }
            }
            if (changed) {
                terrainTextures[row + col * bgWidth].SetData<Color>(touchedTextureData);
            }
        }


        private int GetStartingGlobalX(IGameObject gameObject) {
            return (int)(gameObject.Position.X - gameObject.Origin.X * gameObject.TextureScale);
        }

        private int GetStartingGlobalY(IGameObject gameObject) {
            return (int)(gameObject.Position.Y - gameObject.Origin.Y * gameObject.TextureScale);
        }
    }
}
